﻿using System;
using System.Collections.Generic;

namespace BrickCreator
{
	//Custom brick struct, makes life easier.
	//Comes with error checking, all dat sheet.
	internal struct Brick
	{
		//Properties

		#region Brick Properties

		//Using bytes because these values can never go over 64, 64, and 256, respectively.
		//These are public because they're supposed to be able to change whenever.
		public byte Width, Length, Height;

		//A true/false indicating if the brick is named automatically.
		//When changed to true, all the brick info is automatically set.
		private bool autoui, autocat, autosubcat;

		//String values for brick information
		private string _UiName, _Category, _subCategory;

		#endregion Brick Properties


		#region Brick Accessors

		#region Brick Info Vars

		public string UiName
		{
			get
			{
				if (!autoui)
					return _UiName;
				return this.getAutoUiName();
			}
			set
			{
				if (!autoui)
					_UiName = value;
			}
		}

		public string Category
		{
			get
			{
				if (!autocat)
					return _Category;
				return this.getAutoCategory();
			}
			set
			{
				if (!autocat)
					_Category = value;
			}
		}

		public string subCategory
		{
			get
			{
				if (!autosubcat)
					return _subCategory;
				return this.getAutoSubCategory();
			}
			set
			{
				if (!autosubcat)
					_subCategory = value;
			}
		}

		//You can't change the file names, as there's no point in doing so.
		public string fileName
		{
			get
			{
				return this.getAutoFileName();
			}
		}

		#endregion Brick Info Vars

		//the 3 bools that decide which peices of info are automatically generated.

		#region Autos

		//To save RAM, if auto-generate is on, it doesn't store any of the generated information
		//in the brick structure, as this would be a waste of space if it can be generated on-the-fly.

		public bool AutoUiName
		{
			get
			{
				return autoui;
			}
			set
			{
				if (value && !autoui)
				{
					autoui = true;
					this._UiName = "";
				}
				else if (!value && autoui)
				{
					autoui = false;
					this._UiName = this.getAutoUiName();
				}
				//In all other cases, nothing has to change.
			}
		}

		public bool AutoCategory
		{
			get
			{
				return autocat;
			}
			set
			{
				if (value && !autocat)
				{
					autocat = true;
					this._Category = "";
				}
				else if (!value && autocat)
				{
					autocat = false;
					this._Category = this.getAutoCategory();
				}
				//In all other cases, nothing has to change.
			}
		}

		public bool AutoSubCategory
		{
			get
			{
				return autosubcat;
			}
			set
			{
				if (value && !autosubcat)
				{
					autosubcat = true;
					this._subCategory = "";
				}
				else if (!value && autosubcat)
				{
					autosubcat = false;
					this._subCategory = this.getAutoSubCategory();
				}
				//In all other cases, nothing has to change.
			}
		}

		#endregion Autos

		#endregion Brick Accessors

		//Constructors

		#region Brick Constructors

		//Construct a brick using the 3 basic arguments.
		//wid = width, len = length, hei = height
		public Brick(byte wid, byte len, byte hei)
		{
			//Error Checking

			//All arguments must be >= 1
			if (wid < 1)
				throw new ArgumentOutOfRangeException("wid", "Argument must be >= 1");
			if (len < 1)
				throw new ArgumentOutOfRangeException("len", "Argument must be >= 1");
			if (hei < 1)
				throw new ArgumentOutOfRangeException("hei", "Argument must be >= 1");

			//Width and Length must be <= 64, Height <= 256
			if (wid > 64)
				throw new ArgumentOutOfRangeException("wid", "Argument must be <= 64");
			if (len > 64)
				throw new ArgumentOutOfRangeException("len", "Argument must be <= 64");
			if (hei > 255)
				throw new ArgumentOutOfRangeException("hei", "Argument must be <= 256");

			//Set properties
			autoui = false;
			autocat = false;
			autosubcat = false;

			Width = wid;
			Length = len;
			Height = hei;

			//Fillers, cuz C# is stoopid and won't let me set these through an outside function.
			_UiName = "a";
			_Category = "a";
			_subCategory = "a";

			//Now set all the autos to true and they will all be set automatically.
			autoui = true;
			autocat = true;
			autosubcat = true;
		}

		//Constructor using the 3 basic arguments and 4 strings for the different brick info.
		//AKA manual mode.
		public Brick(byte wid, byte len, byte hei, string ui, string cat, string subcat)
		{
			//Error Checking

			//All arguments must be >= 1
			if (wid < 1)
				throw new ArgumentOutOfRangeException("wid", "Argument must be >= 1");
			if (len < 1)
				throw new ArgumentOutOfRangeException("len", "Argument must be >= 1");
			if (hei < 1)
				throw new ArgumentOutOfRangeException("hei", "Argument must be >= 1");

			//Width and Length must be <= 64, Height <= 256
			if (wid > 64)
				throw new ArgumentOutOfRangeException("wid", "Argument must be <= 64");
			if (len > 64)
				throw new ArgumentOutOfRangeException("len", "Argument must be <= 64");
			if (hei > 255)
				throw new ArgumentOutOfRangeException("hei", "Argument must be <= 256");

			//Check for null shtuff
			if (string.IsNullOrWhiteSpace(cat))
				throw new ArgumentNullException("cat", "Argument cannot be null/whitespace.");
			if (string.IsNullOrWhiteSpace(subcat))
				throw new ArgumentNullException("subcat", "Argument cannot be null/whitespace.");
			if (string.IsNullOrWhiteSpace(ui))
				throw new ArgumentNullException("ui", "Argument cannot be null/whitespace.");

			//End of error checking

			//Set stuff
			//Set the brick dimensions
			Width = wid;
			Length = len;
			Height = hei;

			//Autos should all be false (cuz there are the arguments for the info above)
			autoui = false;
			autocat = false;
			autosubcat = false;

			//Set the brick info
			_Category = cat;
			_subCategory = subcat;
			_UiName = ui;

			//End of setting stuff
		}

		#endregion Brick Constructors

		//This is a series of brick information generation algorithms, that get the specific information for the current brick.
		//It's all based off of a central algorithm that was used to get every property at once in Brick Creator v2.8.

		#region infoAlgorithms

		//This one generates the UI name.
		public string getAutoUiName()
		{
			//Define stuff
			ushort wid = Width, len = Length, hei = Height;
			string ui;

			//Generation algorithm
			if (hei / 3f == len && len == wid && hei >= 12) //It's a Cube!
				ui = wid + "x Cube"; //Since all dimensions are the same, you dont need to display all 3.
			else if (wid > 15 && len > 15 && hei == 1) //Check if it's a Baseplate.
				ui = wid + "x" + len + " Base"; //Base in the name, no "f" extension.
			else if (hei != 1) //Check if the brick is not a plate
			{
				if (hei == 3) //1x height doesnt need the 1x height prefix.
					ui = wid + "x" + len; //Uiname is Width x Length in this case.
				else if (hei / 3 != hei / 3f) //Check if it's not divisible by 3. (In other words, is it a "halfbrick.")
					ui = wid + "x" + len; //Standard UI Name
				else //The brick is a even-studded height.
					ui = wid + "x" + len + "x" + hei / 3; //Make sure they know it's a even height
			}
			else //The brick is a plate
				ui = wid + "x" + len + "f"; //Set the ui name to "flat"

			//Generation of the uiname is done! Return it.
			return ui;
		}

		//This one generates the category, nice and easy!
		public string getAutoCategory()
		{
			//Define stuff
			ushort wid = Width, len = Length, hei = Height;
			string cat;

			//Generation algorithm
			if (hei / 3f == len && len == wid && hei >= 12) //It's a Cube!
				cat = "Baseplates"; //Cubes are in the baseplates category, kind of ironic.
			else if (wid > 15 && len > 15 && hei == 1) //Check if it's a Baseplate.
				cat = "Baseplates"; //It goes in the baseplates section. (Obviously)
			else if (hei != 1) //Check if the brick is not a plate
				cat = "Bricks"; //Set the category to bricks (Because it's not anything else!)
			else //The brick is a plate.
				cat = "Plates";

			//Generation done! Return the value.
			return cat;
		}

		//This one generates subcategory, the most complicated property to generate.
		public string getAutoSubCategory()
		{
			//Define stuff
			ushort wid = Width, len = Length, hei = Height;
			string subCat;

			//Generation algorithm
			if (hei / 3f == len && len == wid && hei >= 12) //It's a Cube!
				subCat = "Cube"; //It's a Cube! Wait, I already said that...
			else if (wid > 15 && len > 15 && hei == 1) //Check if it's a Baseplate.
				subCat = "Plain"; //It goes in "Plain".
			else if (hei != 1) //Check if the brick is not a plate
			{
				if (hei == 3) //1x height doesnt need the 1x height prefix.
					subCat = wid + "x";
				else if (hei % 3 != 0) //Check if it's not divisible by 3. (In other words, is it a "halfbrick.")
				{
					if (hei < 3) //Check if the height is below 1 stud
						subCat = hei + "/3x Height";
					else
						subCat = hei / 3 + " & " + hei % 3 + "/3x Height"; //Splits the height into a full number and a fraction out of 3.
				}
				else //The brick is a even-studded height.
					subCat = hei / 3 + "x Height"; //Make sure they know its a even height
			}
			else //The brick is a plate
				subCat = wid + "x";

			//Generation done! Return the subcategory.
			return subCat;
		}

		//This generates the file name. Not that difficult.
		public string getAutoFileName()
		{
			//Define stuff
			ushort wid = Width, len = Length, hei = Height;
			string fname;

			//Begin generation by checking if it's a cube or baseplate.
			if ((hei / 3f == len && len == wid && hei >= 12) || (wid > 15 && len > 15 && hei == 1))
				fname = wid + "x" + len + "x" + hei; //Regular file name
			else if (hei != 1) //Check if the brick is not a plate
			{
				if (hei == 3) //1x height doesnt need the 1x height prefix.
					fname = wid + "x" + len;
				else //The brick is a even-studded height or a 'halfbrick.'
					fname = wid + "x" + len + "x" + hei;
			}
			else //The brick is a plate
				fname = wid + "x" + len + "f"; // Since its a plate, instead of a height it just displays "f."

			//Generation finished, return the filename.
			return fname;
		}

		#endregion infoAlgorithms
	}

	//Brick manager class, also makes life easier.
	//Sure this is a shitload of code, idc. It makes life easier.
	internal class brickManager
	{
		//Properties

		#region Manager Properties

		public List<Brick> Bricks;
		public ushort brickCount;

		#endregion Manager Properties

		//Constructors

		#region Manager Constructor

		public brickManager()
		{
			//Not much.
			brickCount = 0;
			Bricks = new List<Brick>();
		}

		//Meh, not that exciting so far.

		#endregion Manager Constructor

		//Methods

		#region Manager Methods

		//Add a brick to the manager
		public void addBrick(Brick stuff)
		{
			//Check if it already exists
			//This is a bit of a lengthy check because it needs to check for just dimensions, not everything.
			//Also my very first practical foreach loop! Yay!
			if (Bricks.Count > 0)
			{
				foreach (Brick morestuff in Bricks)
				{
					if (morestuff.Length == stuff.Length && morestuff.Width == stuff.Width &&
						morestuff.Height == stuff.Height)
					{
						throw new ApplicationException("The brick provided to add already exists!");
					}
				}
			}

			//So it doesn't exist. Okay, add it!
			Bricks.Add(stuff);
		}

		//Remove a brick
		public void removeBrick(Brick stuff)
		{
			//If it exists, then we can delete it. Otherwise, nope.
			foreach (Brick morestuff in Bricks)
			{
				if (morestuff.Length == stuff.Length && morestuff.Width == stuff.Width &&
					morestuff.Height == stuff.Height)
				{
					Bricks.Remove(morestuff);
					return;
				}
			}

			throw new ApplicationException("The brick provided to remove does not exist!");
		}

		//Edit a brick
		public void editBrick(Brick old, Brick newbrick)
		{
			//Check if they're the same
			if (old.Equals(newbrick))
				throw new ArgumentException("Both bricks are the same!");

			//SO. they're valid. Sweet.
			//Find the old one in the current list, then replace it with the new one.
			//This one doesn't use foreach tho.
			for (ushort a = 0; a < Bricks.Count; a++)
			{
				Brick morestuff = Bricks[a];

				if (morestuff.Length == old.Length && morestuff.Width == old.Width &&
					morestuff.Height == old.Height)
				{
					//Remove the old one
					Bricks.Remove(morestuff);

					//Replace it with the new one
					Bricks.Insert(a, newbrick);

					//Done! return.
					return;
				}
			}

			//IDK why this would ever happen, but be sure just in case.
			throw new ApplicationException("Fallthrough on for loop in editBrick!");
		}

		//Check if a specific brick structure exists
		public bool brickExists(Brick inQuestion)
		{
			if (Bricks.Count > 0)
				foreach (Brick target in Bricks)
					if (inQuestion.Equals(target))
						return true;
			return false;
		}

		//Check if a brick exists by dimensions
		public bool brickExists(byte Length, byte Width, byte Height)
		{
			if (Bricks.Count > 0)
				foreach (Brick target in Bricks)
					if (target.Length == Length && target.Width == Width && target.Height == Height)
						return true;
			return false;
		}

		#endregion Manager Methods
	}
}